%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR

  jmp loader_start

gdt_start:
  null_desc equ $-gdt_start
  dd  0x00000000
  dd  0x00000000

  code_desc equ $-gdt_start
  dd  0x0000ffff
  dd  DESC_CODE_HIGH4

  stack_desc equ $-gdt_start
  dd  0x00000000
  dd  DESC_STACK_HIGH4

  vedio_desc equ $-gdt_start
  dd  0x80000007
  dd  DESC_VEDIO_HIGH4
gdt_end:

  times 59 dq 0                             	   ;预留59个 define quadra word(8 byte)
  times 5 db 0                                   ;jmp 指令占 3 字节
    
    ;此时偏移为 60*8+4*8=512 刚好是 0x200
    ;程序设置的起始地址为 0x900 那这就是0xb00
  total_mem_bytes  dd 0x0

  ;buf  记录内存大小的缓冲区
  ards_buf times 244 db 0
  ards_nr dw 0					   ;nr 记录20字节结构体个数  计算了一下 4+2+4+244+2=256 刚好256字节
    							   ;书籍作者有强迫症 哈哈 这里244的buf用不到那么多的 实属强迫症使然 哈哈

; selector definition
  SELECTOR_CODE  equ  (code_desc/8<<3)+TI_GDT+RPL0
  SELECTOR_DATA  equ  (stack_desc/8<<3)+TI_GDT+RPL0
  SELECTOR_VEDIO  equ  (vedio_desc/8<<3)+TI_GDT+RPL0

loader_start:
;--------------------------------
;------ get memory size ---------
;--------------------------------

    mov sp,LOADER_BASE_ADDR                                   ;先初始化了栈指针
    xor ebx,ebx                                               ;异或自己 即等于0
    mov ax,0                                       
    mov es,ax                                                 ;心有不安 还是把es给初始化一下
    mov di,ards_buf                                           ;di指向缓冲区位置
.e820_mem_get_loop:
    mov eax,0x0000E820                                            ;每次都需要初始化
    mov ecx,0x14
    mov edx,0x534d4150
    
    
    int 0x15                                                  ;调用了0x15中断
    jc  .e820_failed_so_try_e801                              ;这时候回去看了看jc跳转条件 就是CF位=1 carry flag = 1 中途失败了即跳转
    add di,cx							;把di的数值增加20 为了下一次作准备
    inc word [ards_nr]
    cmp ebx,0
    jne .e820_mem_get_loop                                    ;直至读取完全结束 则进入下面的处理时间
    
    mov cx,[ards_nr]                                          ;反正也就是5 cx足以
    mov ebx,ards_buf
    xor edx,edx
.find_max_mem_area:
    
    mov eax,[ebx]						 ;我也不是很清楚为什么用内存上限来表示操作系统可用部分
    add eax,[ebx+8]                                            ;既然作者这样用了 我们就这样用
    add ebx,20    						 ;简单的排序
    cmp edx,eax
    jge .next_ards
    mov edx,eax

.next_ards:
    loop .find_max_mem_area
    jmp .mem_get_ok
    
.e820_failed_so_try_e801:                                       ;地址段名字取的真的简单易懂 哈哈哈哈 
    mov ax,0xe801
    int 0x15
    jc .e801_failed_so_try_88
   
;1 先算出来低15MB的内存    
    mov cx,0x400
    mul cx                                                      ;低位放在ax 高位放在了dx
    shl edx,16                                                  ;dx把低位的16位以上的书往上面抬 变成正常的数
    and eax,0x0000FFFF                                          ;把除了16位以下的 16位以上的数清零 防止影响
    or edx,eax                                                  ;15MB以下的数 暂时放到了edx中
    add edx,0x100000                                            ;加了1MB 内存空缺 
    mov esi,edx
    
;2 接着算16MB以上的内存 字节为单位
    xor eax,eax
    mov ax,bx
    mov ecx,0x10000                                              ;0x10000为64KB  64*1024  
    mul ecx                                                      ;高32位为0 因为低32位即有4GB 故只用加eax
    mov edx,esi
    add edx,eax
    jmp .mem_get_ok
 
.e801_failed_so_try_88:
     mov ah,0x88
     int 0x15
     jc .error_hlt
     and eax,0x0000FFFF
     mov cx,0x400                                                 ;1024
     mul cx
     shl edx,16
     or edx,eax 
     add edx,0x100000

.error_hlt:
     jmp $
.mem_get_ok:
     mov [total_mem_bytes],edx
; -----------------------------------------------
; --------------- enter protect mode ------------
; -----------------------------------------------

; ----open A20 address line ----
  in al, 0x92
  or al, 0000_0010b
  out 0x92, al

; ----loading gdt ----
; put the start address & limit to #0 of gdt
  mov word [gdt_start], gdt_end-gdt_start-1
  mov dword [gdt_start+2], gdt_start
  lgdt [gdt_start]

; ----set PE of cr0 to 1 ----
  mov eax, cr0
  or eax, 0x00000001b
  mov cr0, eax

; ---- flush pipeline ----
  jmp dword SELECTOR_CODE : p_mode_start

[bits 32]
p_mode_start:
  mov ax, SELECTOR_DATA
  mov ds, ax
  mov es, ax
  mov ss, ax
  mov esp, LOADER_STACK_TOP
  mov ax, SELECTOR_VEDIO
  mov gs, ax

  mov byte [gs:160], 'P'

; -------------------------------------------------
; ----------------- load kernel -------------------
; -------------------------------------------------
  mov eax, KERNEL_START_SECTOR  ; the sector where kernel.bin locate
  mov ebx, KERNEL_BIN_BASE_ADDR

  mov ecx, 200  ; how many setcors load in
  call rd_disk_m_32 

; -------------------------------------------------
; --------------- switch to paging ----------------
; -------------------------------------------------
  call setup_page
  
  sgdt [gdt_start]

  mov ebx, [gdt_start +2]
  or dword [ebx + 0x18 +4], 0xc0000000
  add dword [gdt_start +2], 0xc0000000
  add esp, 0xc0000000

  ;load page directory address to register CR3
  mov eax, PAGE_DIR_TABLE_POS
  mov cr3, eax

  ;enable CR0_PG (bit 31)
  mov eax, cr0
  or eax, 0x80000000
  mov cr0, eax

  mov byte [gs:164], 'V'

  ; flash pipline, update gdt
  lgdt [gdt_start]
  jmp SELECTOR_CODE : enter_kernel


; ------------------------------------------------
; ------------------ functions -------------------
; ------------------------------------------------

; -------------- jump to kernel -------------
enter_kernel:
  call kernel_init

  mov esp, 0xc009f000
  jmp KERNEL_ENTER_ADDR

; -------------- set up page -------------
setup_page:
  mov ecx, 4096
  mov esi, 0
.clear_page_table:
  mov byte [PAGE_DIR_TABLE_POS + esi], 0
  inc esi
  loop .clear_page_table

; ---- create pde(page directory entry) ----
  ; 1.load page 0 to directory entry #0 & #768
  ; 2.load directory address to dirctory entry #1023
  ; 3.load other kernel pde to dirctory entry #769 - #1022

  mov eax, PAGE_DIR_TABLE_POS + 0x1000

  ; ebx save the page table entry base address
  mov ebx, eax
  
  ; ------------ pde ------------------

  or eax, PG_US_U | PG_RW_W | PG_P
  ;pde #0 map to pte #0
  mov [PAGE_DIR_TABLE_POS + 0x0], eax
  ;pde #768 map to pte #0 --- 0xc00/4=768
  mov [PAGE_DIR_TABLE_POS + 0xc00], eax

  mov eax, PAGE_DIR_TABLE_POS
  or eax, PG_US_U | PG_RW_W | PG_P
  ;pde #1023 map to pde base address --- 0xffc/4=1023
  mov [PAGE_DIR_TABLE_POS + 0xffc], eax

  ; pde #769-1022 map to pte #1-254
  mov eax, ebx
  add eax, 0x1000 ; pte #1
  or eax, PG_US_U | PG_RW_W | PG_P
  mov ecx, 254      ; 1022-769+1=254
  mov esi, 769
.load_other_kernel_pdt:
  mov [PAGE_DIR_TABLE_POS + esi*4], eax
  inc esi
  add eax, 0x1000
  loop .load_other_kernel_pdt

  ; ------------ pte ------------------

  ; pte #0 map to low 1MB
  mov ecx, 256  ; 1MB/4KB=256
  mov esi, 0
  mov edx, PG_US_U | PG_RW_W | PG_P
.create_pte:
  mov [ebx + esi*4], edx
  inc esi
  add edx, 0x1000
  loop .create_pte

  ret ; function .setup_page end

; ------------------------
; ---- Load to memory ----
; ------------------------
rd_disk_m_32:
      ;eax=LBA扇区号
      ;ebx=将数据写入的内存地址
      ;cx=读入的扇区数
  mov esi, eax ;备份扇区号
  mov di, cx   ;备份扇区数

  ;写入扇区数
  mov dx, 0x1f2
  mov al, cl
  out dx, al

  mov eax, esi ;还原eax

  ;LBA 0-7
  mov dx, 0x1f3
  out dx, al

  ;LBA 8-15
  mov cl,8
  shr eax,cl
  mov dx, 0x1f4
  out dx, al

  ;LBA 16-23
  shr eax, cl
  mov dx, 0x1f5
  out dx, al

  ;LBA 24-27
  shr eax,cl
  and al,0xf
  ;device 4-7
  or al,0xe0
  mov dx, 0x1f6
  out dx, al

  ;command--read
  mov al,0x20
  mov dx, 0x1f7
  out dx, al

  ;check harddisk state
.not_ready:
  nop
  in al, dx
  and al,0x88 ;take 4 & 7 bit num
  cmp al,0x08
  jnz .not_ready    ;not ready,keep waiting

  ;从 0x1f0 端口读数据
  mov ax, di
  mov dx, 256
  mul dx
  mov cx, ax

  mov dx, 0x1f0

.go_on_read:
  in ax, dx
  mov [ebx], ax
  add ebx, 2
  loop .go_on_read
  ret

; -------- 将 kernel.bin 中的 segment 拷贝到编译的地址 -------
kernel_init:
  xor eax, eax
  xor ebx, ebx
  xor ecx, ecx
  xor edx, edx
  
; typedef struct elf32_hdr{
;  unsigned char e_ident[EI_NIDENT];0
;  Elf32_Half    e_type;0x10
;  Elf32_Half    e_machine;0x12
;  Elf32_Word    e_version;0x14
;  Elf32_Addr    e_entry;0x18  /* Entry point */
;  Elf32_Off     e_phoff;0x1c
;  Elf32_Off     e_shoff;0x20
;  Elf32_Word    e_flags;0x24
;  Elf32_Half    e_ehsize;0x28
;  Elf32_Half    e_phentsize;0x2a
;  Elf32_Half    e_phnum;0x2c
;  Elf32_Half    e_shentsize;0x2e
;  Elf32_Half    e_shnum;0x30
;  Elf32_Half    e_shstrndx;0x32
;} Elf32_Ehdr;

  ; e_phoff
  mov ebx, [KERNEL_BIN_BASE_ADDR + 0x1c]
  add ebx, KERNEL_BIN_BASE_ADDR

  ; e_phnum
  mov cx, [KERNEL_BIN_BASE_ADDR + 0x2c]

  ; e_phentsize
  mov dx, [KERNEL_BIN_BASE_ADDR + 0x2a]

.each_segment:
  ; if p_type = PT_NULL,this program header not used
  cmp byte [ebx +0], PT_NULL

  je .PTNULL

; typedef struct elf32_phdr{
;  Elf32_Word    p_type; 0x0
;  Elf32_Off     p_offset;0x4
;  Elf32_Addr    p_vaddr;0x8
;  Elf32_Addr    p_paddr;0xc
;  Elf32_Word    p_filesz;0x10
;  Elf32_Word    p_memsz;
;  Elf32_Word    p_flags;
;  Elf32_Word    p_align;
;} Elf32_Phdr;

  ; p_filesz
  push dword [ebx + 0x10]
  ; p_offset
  mov eax, [ebx + 0x4]
  add eax, KERNEL_BIN_BASE_ADDR
  push eax
  ; p_vaddr
  push dword [ebx + 0x8]
  
  ; memcpy(dst, src, size)
  call mem_cpy
  add esp, 0xc
.PTNULL:
  add ebx, edx

  loop .each_segment
  ret

mem_cpy:
  cld
  push ebp
  mov ebp, esp
  push ecx
  mov edi, [ebp + 0x8]  ; dst
  mov esi, [ebp + 0xc]  ; src
  mov ecx, [ebp + 0x10] ; size
  rep movsb

  pop ecx
  pop ebp
  ret
